using System;
using System.Linq.Expressions;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
using FluentSilverlight.ValueConverters;

namespace FluentSilverlight.Expressions
{
    public abstract class FrameworkElementExpression<THIS, TModel, VIEW> 
        where THIS : FrameworkElementExpression<THIS, TModel, VIEW>
        where TModel : class
        where VIEW : FrameworkElement
    {
        protected Expression<Func<TModel, object>> visibleExpression;

        private string name;
        private object dataContext;
        private object tag;
        private double height = double.NaN;
        private double minHeight = 0;
        private double maxHeight = double.PositiveInfinity;
        private double width = double.NaN;
        private double minWidth = 0;
        private double maxWidth = double.PositiveInfinity;
        private HorizontalAlignment horizontalAlignment = System.Windows.HorizontalAlignment.Stretch;
        private VerticalAlignment verticalAlignment = System.Windows.VerticalAlignment.Stretch;
        private Thickness margin;
        private Cursor cursor;

        private readonly List<ISettingsApplier<VIEW>> settingsAppliers = new List<ISettingsApplier<VIEW>>();

        protected PropertyBinder<TModel> propertyBinder;

        protected FrameworkElementExpression() 
            : this(string.Format("Element_{0}", Guid.NewGuid()))
        {
        }

        protected FrameworkElementExpression(string name)
        {
            this.name = name;
        }

        public THIS Visible(Expression<Func<TModel, object>> expression)
        {
            visibleExpression = expression;
            return thisInstance();
        }

        public THIS Name(string value)
        {
            name = value;
            return thisInstance();
        }

        public THIS DataContext(object value)
        {
            dataContext = value;
            return thisInstance();
        }

        public THIS Tag(object value)
        {
            tag = value;
            return thisInstance();
        }

        public THIS Height(double value)
        {
            height = value;
            if (!double.IsNaN(value) && verticalAlignment == System.Windows.VerticalAlignment.Stretch)
                verticalAlignment = System.Windows.VerticalAlignment.Center;
            return thisInstance();
        }

        public THIS MinHeight(double value)
        {
            minHeight = value;
            return thisInstance();
        }

        public THIS MaxHeight(double value)
        {
            maxHeight = value;
            return thisInstance();
        }

        public THIS Width(double value)
        {
            width = value;
            if (!double.IsNaN(value) && horizontalAlignment == System.Windows.HorizontalAlignment.Stretch)
                horizontalAlignment = System.Windows.HorizontalAlignment.Center;
            return thisInstance();
        }

        public THIS MinWidth(double value)
        {
            minWidth = value;
            return thisInstance();
        }

        public THIS MaxWidth(double value)
        {
            maxWidth = value;
            return thisInstance();
        }

        public THIS Margin(double uniformWidth)
        {
            margin = new Thickness(uniformWidth);
            return thisInstance();
        }

        public THIS Margin(double left, double top, double right, double bottom)
        {
            margin = new Thickness(left, top, right, bottom);
            return thisInstance();
        }

        public THIS Apply(ISettingsApplier<VIEW> settingsApplier)
        {
            settingsAppliers.Add(settingsApplier);
            return thisInstance();
        }

        public CursorExpression<THIS> Cursor
        {
            get
            {
                return new CursorExpression<THIS>(thisInstance(), c => cursor = c);
            }
        }

        public HorizontalAlignmentExpression<THIS> HorizontalAlignment
        {
            get
            {
                return new HorizontalAlignmentExpression<THIS>(thisInstance(), a => horizontalAlignment = a);
            }
        }

        public VerticalAlignmentExpression<THIS> VerticalAlignment
        {
            get
            {
                return new VerticalAlignmentExpression<THIS>(thisInstance(), a => verticalAlignment = a);
            }
        }

        protected virtual void BuildUp(VIEW element)
        {
            propertyBinder = new PropertyBinder<TModel>(element);

            settingsAppliers.ForEach(x => x.Apply(element));

            element.Name = name;
            if (cursor != null) element.Cursor = cursor;
            if (dataContext != null) element.DataContext = dataContext;
            if (tag != null) element.Tag = tag;
            element.MinHeight = minHeight;
            element.MaxHeight = maxHeight;
            if(!double.IsNaN(height)) element.Height = height;
            element.MinWidth = minWidth;
            element.MaxWidth = maxWidth;
            if(!double.IsNaN(width)) element.Width = width;
            element.Margin = margin;
            element.HorizontalAlignment = horizontalAlignment;
            element.VerticalAlignment = verticalAlignment;

            BindVisibility(element);
        }

        protected virtual void BindVisibility(VIEW element)
        {
            propertyBinder.SetBindingWithConversion<BooleanToVisibilityConverter>(visibleExpression, UIElement.VisibilityProperty);
        }

        protected abstract THIS thisInstance();

        protected virtual object GetCommandParameter(VIEW item)
        {
            return null;
        }
    }

    public interface ISettingsApplier<VIEW> where VIEW : FrameworkElement
    {
        void Apply(VIEW element);
    }
}